using System;
using Microsoft.Isam.Esent.Interop;
using System.Reflection;
using System.Text;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Isam.Esent.Serialization
{
	static class Ext
	{
		public static readonly CreateIndexGrbit JET_bitIndexTuples = (CreateIndexGrbit)( 0x1000 );

		public static readonly JET_param JET_paramIndexTuplesLengthMin = (JET_param)( 110 );
		public static readonly JET_param JET_paramIndexTuplesLengthMax = (JET_param)( 111 );
		public static readonly JET_param JET_paramIndexTuplesToIndexMax = (JET_param)( 112 );

		// http://blogs.msdn.com/laurionb/archive/2009/02/10/cheaply-estimating-the-number-of-records-in-a-table.aspx
		/* public static int ErrGetApproximateRecordCount( JET_SESID sesid, JET_TABLEID tableid, int cSamples )
		{
			long cRecordsTotal = 0;
			JET_RECPOS recpos = new JET_RECPOS();
			for( int i = 0; i < cSamples; i++ )
			{
				recpos.centriesTotal = cSamples + 1;
				recpos.centriesLT = i + 1;

				Api.JetGotoPosition( sesid, tableid, recpos );
				Api.JetGetRecordPosition( sesid, tableid, out recpos );
				cRecordsTotal += recpos.centriesTotal;
			}

			return (int)( cRecordsTotal / cSamples );
		} */

		public static void ExportTable( this Microsoft.Isam.Esent.Serialization.iSerializerSession sess, Type t, string fn )
		{
			using( System.IO.Stream s = new System.IO.FileStream( fn, System.IO.FileMode.Create ) )
				sess.ExportTable( t, s, Microsoft.Isam.Esent.Serialization.ImportExportFormat.TSV );
		}

		public static void ExportAll<T>( this Microsoft.Isam.Esent.Serialization.Recordset<T> rs, string fn ) where T : new()
		{
			rs.session.ExportTable( typeof( T ), fn );
		}
	}
}

public static partial class EsentSerializationUtils
{
	/// <summary>Escape the input ASCII data.</summary>
	/// <param name="buff"></param>
	/// <param name="cbSize"></param>
	/// <returns>string where all the weird characters are escaped with \## </returns>
	public static string EscapeASCII( byte[] buff, int cbSize )
	{
		StringBuilder sb = new StringBuilder();
		foreach( var c in buff.Take( cbSize ) )
		{
			if( c == '\\' || c == '/' || c < 0x20 /* || ( c >=0x7F && c < 0xA0 ) */ )
			{
				sb.Append( '\\' );
				sb.Append( ( (int)c ).ToString( "X2" ) );
			}
			else
				sb.Append( (char)( c ) );
		}
		return sb.ToString();
	}

	// Below are the routines to convert the text data between ESENT column and TSV file.
	// Remember, the ESE strings may contain e.g. any count of \0, it doesn't have any associated encoding
	// (e.g. objects are XmlSerialized into ASCII columns in  UTF8 format),

	/// <summary>Unescape ASCII data</summary>
	/// <param name="chars"></param>
	/// <returns></returns>
	public static IEnumerable<byte> UnescapeASCII( IEnumerable<char> chars )
	{
		IEnumerator<char> enm = chars.GetEnumerator();

		while( true )
		{
			if( !enm.MoveNext() ) yield break;
			char c = enm.Current;
			if( c != '\\' )
			{
				yield return (byte)c;
				continue;
			}
			char[] cc = new char[ 2 ];
			if( !enm.MoveNext() ) yield break;
			cc[ 0 ] = enm.Current;
			if( !enm.MoveNext() ) yield break;
			cc[ 1 ] = enm.Current;

			int res = Convert.ToInt32( new String( cc, 0, 2 ), 16 );
			yield return (byte)( res );
		}
	}

	/// <summary>Escape USC2 Unicode data so it doesn't break the TSV file structure.</summary>
	/// <param name="buff"></param>
	/// <param name="cbSize"></param>
	/// <returns></returns>
	public static string EscapeUnicode( byte[] buff, int cbSize )
	{
		if( 0 != ( cbSize % 2 ) )
		{
			// throw new ArgumentException( "Odd number of bytes in the unicode string" );
			buff[ cbSize ] = 0;
			cbSize++;
		}

		StringBuilder sb = new StringBuilder();
		for( int i = 0; i < cbSize; i += 2 )
		{
			int c = buff[ i + 1 ];
			c = c << 8;
			c |= buff[ i ];

			if( c == '\\' || c == '/' || c < 0x20 /* || ( c >= 0x7F && c < 0xA0 ) */ )
			{
				sb.Append( '\\' );
				sb.Append( ( (int)c ).ToString( "X4" ) );
			}
			else
				sb.Append( (char)( c ) );
		}
		return sb.ToString();
	}

	/// <summary>Unescape Unicode text previously escaped with <see cref="EscapeUnicode" /></summary>
	/// <param name="chars"></param>
	/// <returns></returns>
	public static IEnumerable<byte> UnescapeUnicode( IEnumerable<char> chars )
	{
		IEnumerator<char> enm = chars.GetEnumerator();
		char[] cc = new char[ 4 ];

		while( true )
		{
			if( !enm.MoveNext() )
				yield break;
			int c = enm.Current;
			if( c == '\\' )
			{
				for( int i = 0; i < 4; i++ )
				{
					if( !enm.MoveNext() )
						yield break;
					cc[ i ] = enm.Current;
				}

				c = Convert.ToInt32( new String( cc, 0, 4 ), 16 );
			}

			yield return (byte)( c );
			yield return (byte)( c >> 8 );
		}
	}

	/// <summary>Get the HRESULT code from the <see cref="Exception" /></summary>
	/// <param name="ex"></param>
	/// <returns></returns>
	public static int code( this Exception ex )
	{
		PropertyInfo pi = ex.GetType().GetProperty( "HResult", BindingFlags.Instance | BindingFlags.NonPublic );
		return (int)pi.GetValue( ex, null );
	}
}